/**
 ** ice - An eval compressor
 *
 * Ice is an application for the compression of Javascript.
 *
 */

define(
'oj/ice',
[
'oj/element',
'oj/perf',
'oj/file'
],
function (element, perf, file) {
    'use strict';

    var view = {};

    function ice() {

        var RE = /(?=(([\s\S]+)(?:[\s\S]*?\2)+))/g,
            SUBILLEGAL = '\\[]()+*.{}|^$',
            SUBLIMIT = 255,
            MIN_FPS = 2,
            frameSize = 999,
            sanatisedInput = view.input.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"'),
            buf = sanatisedInput.substring(0, frameSize),
            sub, term, terms = [],
            foundterms = {},
            unusedDict = [],
            i, t, len, score, task = 0,
            fps;

        function escapeRE(term) {
            return term.replace(/(\\|\[|\]|\(|\)|\+|\*|\||\"|\^|\$|\.)/g, '\\$1');
        }

        function findTerms() {
            view.taskStatus0.innerHTML = 'found ' + terms.length + ' terms: ' + ~~ ((RE.lastIndex / view.input.value.length) * 100) + '%';
            sub = RE.exec(buf);

            if (sub === null) {
                // End-of-buffer reached, move on to next task
                i = 0;
                view.taskStatus0.innerHTML = 'found ' + terms.length + ' terms: 100%';
                task++;
                return false;
            }

            term = escapeRE(sub[2])
            len = term.length;

            RE.lastIndex++;
            frameSize++;
            
            buf = sanatisedInput.substring(0, frameSize);

            if (foundterms.hasOwnProperty(term)) {
                // Reject terms that have already been checked
                return true;
            }

            foundterms[term] = true;

            if (len > 2) {
                // Calculate score of term length * occurance
                score = (buf.split(term).length - 1) * len;

                if (score < len * 2) {
                    // Skip anything that scores lower than twice its length
                    return true;
                }

                // Find order of importance in results based on score
                for (i = 0; i < terms.length; i++) {
                    if (score >= terms[i][2]) {
                        // Insert entry in to results
                        terms.splice(i, 0, [
                        term, '', score, element.create('pre', 'entry', ['=' + score + '"' + term + '"'])]);
                        view.table.insertBefore(terms[i][3], terms[i + 1][3]);
                        return true;
                    }
                }

                term = [
                term, '', score, element.create('pre', 'entry', ['=' + score + '"' + term + '"'])];
                terms.push(term);
                view.table.appendChild(term[3]);
            }

            return true;
        }

        function substitute() {
            view.taskStatus1.textContent = 'substituted ' + i + ' terms: ' + ~~ ((i / terms.length) * 100) + '%';

            if (unusedDict.length === 1 || i === terms.length) {
                view.taskStatus1.textContent = 'substituted ' + i + ' terms: 100%';
                while (i < terms.length) {
                    terms[i][3].style.background = 'lightgrey';
                    terms[i][3].style.color = 'grey';
                    terms.splice(i, 1);
                }
                task++;
                return false;
            }

            if (buf.split(terms[i][0]).length < 3) {
                terms[i][3].style.background = 'lightgrey';
                terms[i][3].style.color = 'grey';
                terms.splice(i, 1);
                return true;
            }

            terms[i][1] = unusedDict.shift();
            terms[i][3].innerHTML = i + '.[' + terms[i][1] + ']=' + terms[i][2] + '"' + terms[i][0] + '"';
            buf = buf.replace(new RegExp(escapeRE(terms[i][0]), 'g'), terms[i][1]);

            i++;
            return true;
        }

        function update(time) {
            fps = ~~(perf.fps / MIN_FPS);
            
            switch (task) {
            case 0:
                // start with a clean state
                view.output.textContent = '';
                updateoutlbl();
                view.table.innerHTML = '';
                view.taskStatus0.textContent = '';
                view.taskStatus1.textContent = '';
                view.taskStatus2.textContent = '';
                view.taskStatus3.textContent = '';
                view.taskStatus4.textContent = '';

                // Find substitution chars from chars 33 to 255 that are
                // not used in the input
                i = SUBLIMIT;
                while (i-- > 32) {
                    if (i === 160) continue;
                    t = String.fromCharCode(i);
                    if (SUBILLEGAL.indexOf(t) === -1 && sanatisedInput.indexOf(t) === -1) {
                        unusedDict.push(t);
                    }
                }

                task++;
                break;

            case 1:
                // find repeating terms in buffer
                while (fps-- && findTerms()) {}
                break;

            case 2:
                // assign chars and substitute into buffer
                while (fps-- && substitute()) {}
                break;

            case 3:
                // cap buffer with eval decrush code
                i = terms.length;
                while (i--) {
                    buf = terms[i][1] + terms[i][0] + unusedDict[0] + buf;
                }
                buf = ['(function(i,c,e){', 'c=i.pop();', 'while(e=i.pop()){', 'c=c.replace(RegExp(e[0],"g"),e.slice(1));', '}', 'eval(c);', '})("', buf, '".split("' + unusedDict[0] + '"));'].join('');
                task++;
                break;

            case 4:
                // finished compression
                view.btn.value = 'Run';
                view.output.textContent = buf;
                updateoutlbl();
                view.results.style.display = 'block';
                oj.stop();
                break;
            }
            perf.update(time);
        }
        oj.start(update);
    }
    
    function startstop() {
        view.results.style.display = 'none';
        if (view.btn.value === 'Run') {
            view.btn.value = 'Stop';
            return ice();
        }
        view.btn.value = 'Run';
        oj.stop();
        updateoutlbl();
    }
    
    function updateinlbl() {
        view.inlbl.textContent = 'Input  (' + view.input.value.length + ' bytes)';
    }

    function updateoutlbl() {
        view.outlbl.textContent = 'Result  (' + view.output.textContent.length + ' bytes)';
    }

    function testuncompress() {
        element.create(document.body, 'script', {
            'type': 'text/javascript'
        }, [view.output.textContent]);
    }

    view = {
        table: null,
        btn: null,
        inlbl: null,
        input: null,
        outlbl: null,
        output: null,
        termsdiv: null,
        panel: null,
        taskStatus0: null,
        taskStatus1: null,
        taskStatus2: null,
        taskStatus3: null,
        taskStatus4: null,
        uncompress_btn: null,
        uncompress_output: null
    };

    element.create(
    document.body, 'div', 'resources', [
    element.create('div', 'title', ['Resources']), element.create('div', 'txt', ['whitespace<br/>', '<a href="http://fmarcia.info/jsmin/test.html">jsmin</a><br/>', '<br/>', 'Identifier shortners<br/>', '<a href="http://refresh-sf.com/yui/">YUI compressor</a><br/>', '<a href="http://base2.googlecode.com/svn/trunk/src/apps/packer/packer.html">Packer</a><br/>', '<a href="http://shrinksafe.dojotoolkit.org/">Dojo shrinksafe</a><br/>', '<br/>', 'Compilers<br/>', '<a href="http://www.lotterypost.com/js-compress.aspx">Ajax Minifier</a><br/>', '<a href="http://closure-compiler.appspot.com/">Google Closure</a><br/>', '<a href="http://marijnhaverbeke.nl/uglifyjs">UglifyJS</a><br/>', '<br/>', 'Eval compression<br/>', '<a href="http://www.iteral.com/jscrush/">jscrush</a><br/>', '<a href="http://www.brainjar.com/js/crunch/demo.html">jscrunch</a><br/>', '<br/>', 'Utilities<br/>', '<a href="http://www.jslint.com/">jslint</a><br/>', '<a href="http://jshint.com/">jshint</a><br/>', '<a href="http://jsbeautifier.org/">jsbeautifier</a><br/>'])]);

    element.create(
    document.body, 'div', ['evil.js by <a href="tribsloft.appspot.com">Tribs</a>, an eval compressor.']);
    element.create(document.body, 'div', 'left', [
    view.inlbl = element.create('div', 'title'), element.create('div', 'intxt', [view.input = element.create('textarea', {
        'spellcheck': 'false'
    })])]);
    view.panel = element.create(document.body, 'div', 'info', [
    view.btn = element.create(document.body, 'input', 'run', {
        'type': 'button',
        'value': 'Run'
    }), element.create('div', [
    view.taskStatus0 = element.create('div', 'status'), view.taskStatus1 = element.create('div', 'status'), view.taskStatus2 = element.create('div', 'status'), view.taskStatus3 = element.create('div', 'status'), view.taskStatus4 = element.create('div', 'status')]), perf.meter]);
    view.termsdiv = element.create(document.body, 'div', 'left', [
    element.create('div', 'title', ['Terms']), view.table = element.create('div', 'terms')]);
    view.results = element.create(document.body, 'div', 'left results', [
    view.outlbl = element.create('div', 'title'), view.output = element.create('div', 'output'), view.uncompress_btn = element.create('input', {
        'type': 'button',
        'value': 'Test'
    }), element.create('div', 'title', ['Uncompressed']), view.uncompress_output = element.create('div', 'txt')]);

    // wire up DOM events to functions
    view.btn.onclick = startstop;
    view.uncompress_btn.onclick = testuncompress;
    view.input.onkeyup = updateinlbl;
    view.output.onkeyup = updateoutlbl;

    // initialise label states
    updateinlbl();
    updateoutlbl();


    element.create(document.head, 'style', {'type': 'text/css'}, [
        'body{margin:5px}',
        'textarea{width:100%;height:200px}',
        '.title{border-bottom:1px solid grey;margin-top:10px;margin-bottom:5px}',
        '.left{width:80%}',
        '.resources{float:right}',
        '.terms{height:150px;overflow-y:scroll}',
        '.entry{white-space:pre;border-bottom:1px solid lightgrey;padding:0;margin:0}',
        '.info{width:80%}',
        '.perf_meter{display:block;height:100px;right:20%;width:80%}',
        '.results{display:none}',
        '.output{font-family:monospace;white-space:pre-wrap}'
    ]);

});
